package cc.verywell.pureblock.msg.nfs.linkmap;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

import cc.verywell.pureblock.msg.nfs.strc.EncodeAble;

/**
* 
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年6月16日 上午11:53:38
*/
public class LinkCell implements EncodeAble,Comparable<LinkCell> {
	final static public LinkCell MIN = new LinkCell(new byte[0]) {
		@Override
		public int compareTo(LinkCell c) {
			return -1;
		}
		@Override
		public int compareTo(byte[] u, int offset, int length) {
			return -1;
		}
		@Override
		public void encode(ByteBuffer bb) {throw new IllegalAccessError();}
		@Override
		public int encodeSize() {throw new IllegalAccessError();}
		@Override
		public byte[] getKey() {throw new IllegalAccessError();}
		@Override
		public byte[] getLinkfrom() {throw new IllegalAccessError();}
		@Override
		public int getPrierty() {throw new IllegalAccessError();}
		@Override
		public long getSeq() {throw new IllegalAccessError();}
		@Override
		public boolean isRemove() {throw new IllegalAccessError();}
		@Override
		public boolean isStable() {throw new IllegalAccessError();}
	};
	
	final public byte[] encoded;
	//final public int keyoff,keylength,linkoff,linklength;
	
	public LinkCell(byte[] encoded) {
		this.encoded = encoded;
	}
	static public LinkCell createLink0(byte[] key) {
		int size = lengthSize(key.length)+key.length+1;
		byte[] encoded = new byte[size];
		ByteBuffer bb = ByteBuffer.wrap(encoded);
		
		lengthEncodeInt(bb,key.length);
		bb.put(key);
		bb.put((byte)0);
		
		if(bb.hasRemaining())throw new RuntimeException();
		
		return new LinkCell(encoded);
	}
	static public LinkCell createKey(byte[] key) {
		int size = lengthSize(key.length)+key.length;
		byte[] encoded = new byte[size];
		ByteBuffer bb = ByteBuffer.wrap(encoded);
		
		lengthEncodeInt(bb,key.length);
		bb.put(key);
		
		if(bb.hasRemaining())throw new RuntimeException();
		
		return new LinkCell(encoded);
	}
	static public LinkCell createNoSeq(LinkCell cell) {
		return new LinkCell(Arrays.copyOf(cell.encoded, cell.encoded.length-8));
	}
	
	
	public LinkCell(byte[] key, long seq, boolean isRemove) {
		this(key,null,0,seq,isRemove);
	}
	public LinkCell(byte[] key, byte[] linkfrom, int prierty, long seq, boolean isRemove) {
		assert seq>0; //"easyTable is End-Life, please copy all data to a new one";
		int size;
		if(linkfrom!=null)size = lengthSize(key.length)+key.length+lengthSize(prierty)+lengthSize(linkfrom.length)+linkfrom.length+8;
		else size = lengthSize(key.length)+key.length+2+8;
		encoded = new byte[size];
		ByteBuffer bb = ByteBuffer.wrap(encoded);
		
		lengthEncodeInt(bb,key.length);
		//keyoff = bb.position();
		//keylength = key.length;
		bb.put(key);
		if(linkfrom!=null) {
			lengthEncodeInt(bb,prierty);
			lengthEncodeInt(bb,linkfrom.length);
			//linkoff = bb.position();
			//linklength = linkfrom.length;
			bb.put(linkfrom);
		}else {
			//linkoff = -1;
			//linklength = 0;
			bb.put((byte)0);
			bb.put((byte)0);
		}
		bb.order(ByteOrder.BIG_ENDIAN);
		bb.putLong((~seq)<<1 | (isRemove?1:0));
		
		if(bb.hasRemaining())throw new RuntimeException();
	}
	public boolean isRemove() {
		return 0!=(encoded[encoded.length-1]&1);
	}
	public long getSeq() {
		ByteBuffer bb = ByteBuffer.wrap(encoded,encoded.length-8,8);
		bb.order(ByteOrder.BIG_ENDIAN);
		return (~bb.getLong())>>>1;
	}
	public boolean isStable() {
		ByteBuffer bb = ByteBuffer.wrap(encoded);
		int i = lengthDecodeInt(bb);
		bb.position(bb.position()+i);
		lengthJump(bb);
		return 0==lengthDecodeInt(bb);
	}
	public byte[] getKey() {
		ByteBuffer bb = ByteBuffer.wrap(encoded);
		int i = lengthDecodeInt(bb);
		byte[] key = new byte[i];
		bb.get(key);
		return key;
	}
	public byte[] getLinkfrom() {
		ByteBuffer bb = ByteBuffer.wrap(encoded);
		int i = lengthDecodeInt(bb);
		bb.position(bb.position()+i);
		lengthJump(bb);
		i = lengthDecodeInt(bb);
		if(i==0)return null;
		byte[] key = new byte[i];
		bb.get(key);
		return key;
	}
	public int getPrierty() {
		ByteBuffer bb = ByteBuffer.wrap(encoded);
		int i = lengthDecodeInt(bb);
		bb.position(bb.position()+i);
		return lengthDecodeInt(bb);
	}
	
	@Override
	public int encodeSize() {
		return encoded.length;
	}
	@Override
	public void encode(ByteBuffer bb) {
		bb.put(encoded);
	}
	@Override
	public int compareTo(LinkCell c) {
        if (this == c)return 0;
        byte[] m = this.encoded,u = c.encoded;

        int l = Math.min(m.length, u.length);
        for (int i = 0; i < l; i++) {
            int mi = 0xFF & m[i];
            int ui = 0xFF & u[i];
            if (mi != ui) {
                return mi - ui;
            }
        }
        return m.length - u.length;
	}
	public int compareTo(byte[] u,int offset,int length) {
		byte[] m = this.encoded;
		int l = Math.min(m.length, length);
		for (int i = 0; i < l; i++) {
            int mi = 0xFF & m[i];
            int ui = 0xFF & u[i+offset];
            if (mi != ui) {
                return mi - ui;
            }
        }
        return m.length - length;
	}
	public boolean startWith(LinkCell link0) {
		if (this == link0)return true;
        byte[] m = this.encoded,u = link0.encoded;

        if(m.length<u.length)return false;
        for (int i = 0; i < u.length; i++) {
            if (m[i] != u[i]) {
                return false;
            }
        }
        return true;
	}
	private int hash;
	@Override
	public int hashCode() {
		if(hash!=0)return hash;
		return hash = Arrays.hashCode(encoded);
	}
	@Override
	public boolean equals(Object obj) {
		if(this==obj)return true;
		if(obj.getClass()!=this.getClass())return false;
		return Arrays.equals(this.encoded, ((LinkCell)obj).encoded );
	}
    public boolean equalsWithoutSeq(LinkCell t,int startAt) {
        if (this==t)
            return true;
        if (t==null)
            return false;
        byte[] m = this.encoded,u = t.encoded;
        if(m.length!=u.length)return false;
        int length = m.length-8;
        for (int i = startAt; i < length; i++) {
            if (m[i] != u[i]) {
                return false;
            }
        }
        return true;
    }
    static public int startWith(byte[] m,int l,LinkCell t,int startAt) {
    	byte[] u = t.encoded;
    	if(u.length<l)l = u.length;
    	for (int i = startAt; i < l; i++) {
            if (m[i] != u[i]) {
                return i-1;
            }
        }
    	return l;
    }
    static public int smallerAndStartWith(byte[] m,int l,LinkCell t,int startAt) {
    	byte[] u = t.encoded;
    	if(u.length<l)l = u.length;
    	for (int i = startAt; i < l; i++) {
            int mi = 0xFF & m[i];
            int ui = 0xFF & u[i];
            if (mi == ui) {
            }else if (mi<ui) {
            	return i-1;
            }else {
            	return -1;
            }
        }
    	return l;
    }

	
	static public int lengthSize(int length) {
		if(length<0)throw new IllegalArgumentException();
		if(length<0b10000000)return 1;
		if(length<0b100000000000000)return 2;
		if(length<0b1000000000000000000000)return 3;
		if(length<0b10000000000000000000000000000)return 4;
		return 5;
	} 
	static public int lengthSize(long length) {
		if(length<0)throw new IllegalArgumentException();
		int i = 1;
		while((length>>>=7)!=0)i++;
		return i;
	} 
	static public void lengthEncodeInt(ByteBuffer bb,int length) {
		int t = 0b01111111 & length;
		length >>>= 7;
		while(length!=0) {
			bb.put((byte)(t & 0b10000000));
			t = 0b01111111 & length;
			length >>>= 7;
		}
		bb.put((byte)t);
	}
	static public void lengthEncodeLong(ByteBuffer bb,long length) {
		int t = 0b01111111 & (byte)length;
		length >>>= 7;
		while(length!=0) {
			bb.put((byte)(t & 0b10000000));
			t = 0b01111111 & (int)length;
			length >>>= 7;
		}
		bb.put((byte)t);
	}
	static public void lengthJump(ByteBuffer bb) {
		while((0b10000000 & bb.get())!=0);
	}
	static public int lengthDecodeInt(ByteBuffer bb) {
		int t = 0xFF & bb.get();
		int out = 0b01111111 & t;
		while((0b10000000 & t)!=0) {
			t = 0xFF & bb.get();
			out <<= 7;
			out |= 0b01111111 & t;
		}
		return out;
	}
	static public long lengthDecodeLong(ByteBuffer bb) {
		int t = 0xFF & bb.get();
		long out = 0b01111111 & t;
		while((0b10000000 & t)!=0) {
			t = 0xFF & bb.get();
			out <<= 7;
			out |= 0b01111111 & t;
		}
		return out;
	}}
